﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using TtfLibrary.CommonStructures;
using TtfLibrary.Tables;

namespace TtfLibrary
{
    public class TtfFileReader
    {
        public TtfFile TtfFile;
        public byte[] Content;
        public TtfStructureReader Reader;

        public TtfFileReader(TtfFile file, Stream input)
        {
            TtfFile = file;

            var buffer = new MemoryStream();
            input.CopyTo(buffer);
            SetContent(buffer.ToArray());
        }

        public TtfFileReader(TtfFile file, string fn)
        {
            TtfFile = file;
            SetContent(File.ReadAllBytes(fn));
        }

        private void SetContent(byte[] content)
        {
            Content = content;
            Reader = new TtfStructureReader() { Content = Content };
        }

        public void Read()
        {
            TtfFile.OffsetTable = Reader.Read<TtfOffsetTable>();

            for (int i = 0; i < TtfFile.OffsetTable.NumTables; i++)
            {
                var td = Reader.Read<TtfTableDictionary>();
                TtfFile.TableDictionaries.Add(td.TagAsString, td);
                TtfFile.TableDictionaryNamesInOrder.Add(td.TagAsString);
            }

            TtfFile.Head = ReadTable<OttHead>("head");
            TtfFile.Hhea = ReadTable<OttHhea>("hhea");
            TtfFile.Maxp = ReadTable<OttMaxp>("maxp");
            TtfFile.Hmtx = ReadTable<OttHmtx>("hmtx", (table) => table.Read(Reader, TtfFile.Hhea.NumberOfHMetrics, TtfFile.Maxp.NumGlyphs));
            TtfFile.OS2 = ReadTable<OttOS2>("OS/2");
            TtfFile.Post = ReadTable<OttPost>("post");
            TtfFile.Name = ReadTable<OttName>("name", null, false);
            TtfFile.Loca = ReadTable<OttLoca>("loca", (table) => table.Read(Reader, TtfFile.Maxp.NumGlyphs + 1, TtfFile.Head.IndexToLocFormat == 1));
            TtfFile.Cmap = ReadTable<OttCmap>("cmap", null, false);

            var baseGlyfOffset = (int)TtfFile.TableDictionaries["glyf"].Offset;
            var glyfData = Reader.Content.Skip((int)TtfFile.TableDictionaries["glyf"].Offset).Take((int)TtfFile.TableDictionaries["glyf"].Length).ToArray();
            TtfFile.Glyf = ReadTable<OttGlyf>("glyf", (table) => table.Read(Reader,
                TtfFile.Loca.Offsets.Take(TtfFile.Loca.Offsets.Count - 1).Select(x => (int)(baseGlyfOffset + x * 2)).ToArray()), false);

            var notReadedTables = TtfFile.TableDictionaryNamesInOrder.Except(ReadedTables);
            foreach (var tableName in notReadedTables)
                ReadTable<OttUnknown>(tableName);

            TtfFile.TableOrders = TtfFile.TableDictionaries.Values.OrderBy(td => td.Offset).Select(td => td.TagAsString).ToList();
        }

        public List<string> ReadedTables = new List<string>();

        public T ReadTable<T>(string tableName, Action<T> afterRead = null, bool checkLength = true)
        {
            TtfTableDictionary tableDict;
            if (!TtfFile.TableDictionaries.TryGetValue(tableName, out tableDict))
                return default(T);

            Reader.Position = (int)tableDict.Offset;
            TtfFile.TableContents[tableName] = Reader.ReadBytes((int)tableDict.Length);

            Reader.Position = (int)tableDict.Offset;

            var result = Reader.Read<T>();

            if (result is IAfterReadTable)
                ((IAfterReadTable)result).AfterRead(Reader, tableDict);

            if (afterRead != null)
                afterRead(result);

            if (checkLength)
            {
                var readed = Reader.Position - tableDict.Offset;
                // TODO: Error handling
                //if (readed != tableDict.Length)
                //    MessageBox.Show(String.Format("Table {0} bad length, readed: {1}, real length: {2}", tableName, readed, tableDict.Length));
            }

            TtfFile.Tables[tableName] = result;
            ReadedTables.Add(tableName);

            return result;
        }
    }

    public interface IAfterRead
    {
        void AfterRead(TtfStructureReader reader, int startOffset);
    }

    public interface IAfterReadTable
    {
        void AfterRead(TtfStructureReader reader, TtfTableDictionary tableDict);
    }
}
